﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ReaderWriterLockSlim.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the ReaderWriterLockSlim type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Threading
{
    using System;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    public class ReaderWriterLockSlim : IDisposable
    {
        #region Fields

        private const int HashTableSize = 0xff;
        private const int LockSleep0Count = 5;
        private const int LockSpinCount = 10;
        private const int LockSpinCycles = 20;
        private const uint MaxReaderCount = 0xffffffe;
        private const int MaxSpinCount = 20;
        private const uint ReaderMask = 0xfffffff;
        private const uint WaitingUpgrader = 0x20000000;
        private const uint WaitingWriters = 0x40000000;
        private const uint WriterHeld = 0x80000000;
        private readonly bool _isReentrant;
        private bool _isDisposed;
        private int _lock;
        private bool _noWaiters;
        private uint _numReadWaiters;
        private uint _numUpgradeWaiters;
        private uint _numWriteUpgradeWaiters;
        private uint _numWriteWaiters;
        private uint _owners;
        private EventWaitHandle _readEvent;
        private ReaderWriterCount[] _rwc;
        private EventWaitHandle _upgradeEvent;
        private int _upgradeLockOwnerId;
        private bool _upgradeThreadHoldingRead;
        private EventWaitHandle _waitUpgradeEvent;
        private EventWaitHandle _writeEvent;
        private int _writeLockOwnerId;

        #endregion

        public ReaderWriterLockSlim()
            : this(LockRecursionPolicy.NoRecursion)
        {
        }

        public ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy)
        {
            if (recursionPolicy == LockRecursionPolicy.SupportsRecursion)
            {
                _isReentrant = true;
            }
            InitializeThreadCounts();
        }

        public int CurrentReadCount
        {
            get
            {
                var numReaders = (int)GetNumReaders();
                return _upgradeLockOwnerId != -1 ? numReaders - 1 : numReaders;
            }
        }

        public bool IsReadLockHeld
        {
            get { return (RecursiveReadCount > 0); }
        }

        public bool IsUpgradeableReadLockHeld
        {
            get { return (RecursiveUpgradeCount > 0); }
        }

        public bool IsWriteLockHeld
        {
            get { return (RecursiveWriteCount > 0); }
        }

        public LockRecursionPolicy RecursionPolicy
        {
            get { return _isReentrant ? LockRecursionPolicy.SupportsRecursion : LockRecursionPolicy.NoRecursion; }
        }

        public int RecursiveReadCount
        {
            get
            {
                var managedThreadId = Thread.CurrentThread.ManagedThreadId;
                var readercount = 0;
                try
                {
                }
                finally
                {
                    EnterLock();
                    var threadRwCount = GetThreadRwCount(managedThreadId, true);
                    if (threadRwCount != null)
                    {
                        readercount = threadRwCount.ReaderCount;
                    }
                    ExitLock();
                }
                return readercount;
            }
        }

        public int RecursiveUpgradeCount
        {
            get
            {
                var managedThreadId = Thread.CurrentThread.ManagedThreadId;
                if (_isReentrant)
                {
                    var upgradecount = 0;
                    try
                    {
                    }
                    finally
                    {
                        EnterLock();
                        var threadRwCount = GetThreadRwCount(managedThreadId, true);
                        if (threadRwCount != null)
                        {
                            upgradecount = threadRwCount.RecursiveCounts.UpgradeCount;
                        }
                        ExitLock();
                    }
                    return upgradecount;
                }
                return managedThreadId == _upgradeLockOwnerId ? 1 : 0;
            }
        }

        public int RecursiveWriteCount
        {
            get
            {
                var managedThreadId = Thread.CurrentThread.ManagedThreadId;
                var writercount = 0;
                if (_isReentrant)
                {
                    try
                    {
                    }
                    finally
                    {
                        EnterLock();
                        var threadRwCount = GetThreadRwCount(managedThreadId, true);
                        if (threadRwCount != null)
                        {
                            writercount = threadRwCount.RecursiveCounts.WriterCount;
                        }
                        ExitLock();
                    }

                    return writercount;
                }
                return managedThreadId == _writeLockOwnerId ? 1 : 0;
            }
        }

        public int WaitingReadCount
        {
            get { return (int)_numReadWaiters; }
        }

        public int WaitingUpgradeCount
        {
            get { return (int)_numUpgradeWaiters; }
        }

        public int WaitingWriteCount
        {
            get { return (int)_numWriteWaiters; }
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
        }

        #endregion

        private void SetOwnerFlag(uint flag)
        {
            _owners |= flag;
        }

        private void ClearOwnerFlag(uint flag)
        {
            _owners &= ~flag;
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (((WaitingReadCount > 0) || (WaitingUpgradeCount > 0)) || (WaitingWriteCount > 0))
                {
                    throw new SynchronizationLockException(
                        PlatformExceptionMessages.ReaderWriterLock_LockDisposedWhilstStillInUse);
                }

                if ((IsReadLockHeld || IsUpgradeableReadLockHeld) || IsWriteLockHeld)
                {
                    throw new SynchronizationLockException(
                        PlatformExceptionMessages.ReaderWriterLock_LockDisposedWhilstStillInUse);
                }
                CloseHandle(ref _writeEvent);
                CloseHandle(ref _readEvent);
                CloseHandle(ref _upgradeEvent);
                CloseHandle(ref _waitUpgradeEvent);
                _isDisposed = true;
            }
        }

        private static void CloseHandle(ref EventWaitHandle handle)
        {
            if (handle != null)
            {
                handle.Close();
                handle = null;
            }
        }

        private void EnterLock()
        {
            if (Interlocked.CompareExchange(ref _lock, 1, 0) != 0)
            {
                EnterLockSpin();
            }
        }

        private void EnterLockSpin()
        {
            var processorCount = Environment.ProcessorCount;
            var spins = 0;
            while (true)
            {
                if ((spins < LockSpinCount) && (processorCount > 1))
                {
                    Thread.SpinWait(LockSpinCycles * (spins + 1));
                }
                else if (spins < 15)
                {
                    Thread.Sleep(0);
                }
                else
                {
                    Thread.Sleep(1);
                }
                if ((_lock == 0) && (Interlocked.CompareExchange(ref _lock, 1, 0) == 0))
                {
                    return;
                }
                spins++;
            }
        }

        private static void SpinWait(int spinCount)
        {
            if ((spinCount < LockSleep0Count) && (Environment.ProcessorCount > 1))
            {
                Thread.SpinWait(LockSpinCycles * spinCount);
            }
            else if (spinCount < 0x11)
            {
                Thread.Sleep(0);
            }
            else
            {
                Thread.Sleep(1);
            }
        }

        public void EnterReadLock()
        {
            TryEnterReadLock(-1);
        }

        public void EnterUpgradeableReadLock()
        {
            TryEnterUpgradeableReadLock(-1);
        }

        public void EnterWriteLock()
        {
            TryEnterWriteLock(-1);
        }

        private void ExitAndWakeUpAppropriateWaiters()
        {
            if (_noWaiters)
            {
                ExitLock();
            }
            else
            {
                ExitAndWakeUpAppropriateWaitersPreferringWriters();
            }
        }

        private void ExitAndWakeUpAppropriateWaitersPreferringWriters()
        {
            var hasUpgradeWriters = false;
            var hasReadWaiters = false;
            var numReaders = GetNumReaders();
            if ((_isReentrant && (_numWriteUpgradeWaiters > 0)) && (_upgradeThreadHoldingRead && (numReaders == 2)))
            {
                ExitLock();
                _waitUpgradeEvent.Set();
            }
            else if ((numReaders == 1) && (_numWriteUpgradeWaiters > 0))
            {
                ExitLock();
                _waitUpgradeEvent.Set();
            }
            else if ((numReaders == 0) && (_numWriteWaiters > 0))
            {
                ExitLock();
                _writeEvent.Set();
            }
            else if (numReaders >= 0)
            {
                if ((_numReadWaiters == 0) && (_numUpgradeWaiters == 0))
                {
                    ExitLock();
                }
                else
                {
                    if (_numReadWaiters != 0)
                    {
                        hasReadWaiters = true;
                    }
                    if ((_numUpgradeWaiters != 0) && (_upgradeLockOwnerId == -1))
                    {
                        hasUpgradeWriters = true;
                    }
                    ExitLock();
                    if (hasReadWaiters)
                    {
                        _readEvent.Set();
                    }
                    if (hasUpgradeWriters)
                    {
                        _upgradeEvent.Set();
                    }
                }
            }
            else
            {
                ExitLock();
            }
        }

        private void ExitLock()
        {
            _lock = 0;
        }

        public void ExitReadLock()
        {
            var managedThreadId = Thread.CurrentThread.ManagedThreadId;
            EnterLock();
            var threadRwCount = GetThreadRwCount(managedThreadId, true);
            if ((threadRwCount == null) || (threadRwCount.ReaderCount < 1))
            {
                ExitLock();
                throw new SynchronizationLockException(PlatformExceptionMessages.ReaderWriterLock_ReadLockReleasedWhenNotHeld);
            }
            if (_isReentrant)
            {
                if (threadRwCount.ReaderCount > 1)
                {
                    threadRwCount.ReaderCount--;
                    ExitLock();
                    return;
                }
                if (managedThreadId == _upgradeLockOwnerId)
                {
                    _upgradeThreadHoldingRead = false;
                }
            }
            _owners--;
            threadRwCount.ReaderCount--;
            ExitAndWakeUpAppropriateWaiters();
        }

        public void ExitUpgradeableReadLock()
        {
            var managedThreadId = Thread.CurrentThread.ManagedThreadId;
            if (!_isReentrant)
            {
                if (managedThreadId != _upgradeLockOwnerId)
                {
                    throw new SynchronizationLockException(PlatformExceptionMessages.ReaderWriterLock_UpgradeableLockReleasedWhenNotHeld);
                }
                EnterLock();
            }
            else
            {
                EnterLock();
                var threadRwCount = GetThreadRwCount(managedThreadId, true);
                if (threadRwCount == null)
                {
                    ExitLock();
                    throw new SynchronizationLockException(PlatformExceptionMessages.ReaderWriterLock_UpgradeableLockReleasedWhenNotHeld);
                }
                var rc = threadRwCount.RecursiveCounts;
                if (rc.UpgradeCount < 1)
                {
                    ExitLock();
                    throw new SynchronizationLockException(PlatformExceptionMessages.ReaderWriterLock_UpgradeableLockReleasedWhenNotHeld);
                }
                rc.UpgradeCount--;
                if (rc.UpgradeCount > 0)
                {
                    ExitLock();
                    return;
                }
                _upgradeThreadHoldingRead = false;
            }
            _owners--;
            _upgradeLockOwnerId = -1;
            ExitAndWakeUpAppropriateWaiters();
        }

        public void ExitWriteLock()
        {
            var managedThreadId = Thread.CurrentThread.ManagedThreadId;
            if (!_isReentrant)
            {
                if (managedThreadId != _writeLockOwnerId)
                {
                    throw new SynchronizationLockException(PlatformExceptionMessages.ReaderWriterLock_WriteLockReleasedWhenNotHeld);
                }
                EnterLock();
            }
            else
            {
                EnterLock();
                var threadRwCount = GetThreadRwCount(managedThreadId, false);
                if (threadRwCount == null)
                {
                    ExitLock();
                    throw new SynchronizationLockException(PlatformExceptionMessages.ReaderWriterLock_WriteLockReleasedWhenNotHeld);
                }
                var rc = threadRwCount.RecursiveCounts;
                if (rc.WriterCount < 1)
                {
                    ExitLock();
                    throw new SynchronizationLockException(PlatformExceptionMessages.ReaderWriterLock_WriteLockReleasedWhenNotHeld);
                }
                rc.WriterCount--;
                if (rc.WriterCount > 0)
                {
                    ExitLock();
                    return;
                }
            }
            ClearOwnerFlag(WriterHeld);
            _writeLockOwnerId = -1;
            ExitAndWakeUpAppropriateWaiters();
        }

        private uint GetNumReaders()
        {
            return (_owners & ReaderMask);
        }

        private ReaderWriterCount GetThreadRwCount(int id, bool dontAllocate)
        {
            var index = id & HashTableSize;
            ReaderWriterCount count = null;
            if (_rwc[index] == null)
            {
                if (dontAllocate)
                {
                    return null;
                }
                _rwc[index] = new ReaderWriterCount(_isReentrant);
            }
            if (_rwc[index].ThreadId == id)
            {
                return _rwc[index];
            }
            if (_rwc[index].IsEmpty && !dontAllocate)
            {
                if (_rwc[index].Next == null)
                {
                    _rwc[index].ThreadId = id;
                    return _rwc[index];
                }
                count = _rwc[index];
            }
            var next = _rwc[index].Next;
            while (next != null)
            {
                if (next.ThreadId == id)
                {
                    return next;
                }
                if ((count == null) && next.IsEmpty)
                {
                    count = next;
                }
                next = next.Next;
            }
            if (dontAllocate)
            {
                return null;
            }
            if (count == null)
            {
                next = new ReaderWriterCount(_isReentrant)
                    {
                        ThreadId = id,
                        Next = _rwc[index].Next
                    };
                _rwc[index].Next = next;
                return next;
            }
            count.ThreadId = id;
            return count;
        }

        private void InitializeThreadCounts()
        {
            _rwc = new ReaderWriterCount[0x100];
            _upgradeLockOwnerId = -1;
            _writeLockOwnerId = -1;
        }

        private static bool IsRwHashEntryChanged(ReaderWriterCount lrwc, int id)
        {
            return (lrwc.ThreadId != id);
        }

        private bool IsWriterAcquired()
        {
            return ((_owners & 0xbfffffff) == 0);
        }

        private void LazyCreateEvent(ref EventWaitHandle waitEvent, bool makeAutoResetEvent)
        {
            ExitLock();
            var handle = makeAutoResetEvent ? (EventWaitHandle)new AutoResetEvent(false) : new ManualResetEvent(false);
            EnterLock();
            if (waitEvent == null)
            {
                waitEvent = handle;
            }
            else
            {
                handle.Close();
            }
        }

        public bool TryEnterReadLock(int millisecondsTimeout)
        {
            return TryEnterReadLock(new TimeoutTracker(millisecondsTimeout));
        }

        private bool TryEnterReadLock(TimeoutTracker timeout)
        {
            return TryEnterReadLockCore(timeout);
        }

        public bool TryEnterReadLock(TimeSpan timeout)
        {
            return TryEnterReadLock(new TimeoutTracker(timeout));
        }

        private bool TryEnterReadLockCore(TimeoutTracker timeout)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(null);
            }
            ReaderWriterCount lrwc;
            var managedThreadId = Thread.CurrentThread.ManagedThreadId;
            if (!_isReentrant)
            {
                if (managedThreadId == _writeLockOwnerId)
                {
                    throw new LockRecursionException(PlatformExceptionMessages.ReaderWriterLock_ReadLockNotAllowedWhenWriteLockHeld);
                }
                EnterLock();
                lrwc = GetThreadRwCount(managedThreadId, false);
                if (lrwc.ReaderCount > 0)
                {
                    ExitLock();
                    throw new LockRecursionException(PlatformExceptionMessages.ReaderWriterLock_RecursiveReadLockNotAllowed);
                }
                if (managedThreadId == _upgradeLockOwnerId)
                {
                    lrwc.ReaderCount++;
                    _owners++;
                    ExitLock();
                    return true;
                }
            }
            else
            {
                EnterLock();
                lrwc = GetThreadRwCount(managedThreadId, false);
                if (lrwc.ReaderCount > 0)
                {
                    lrwc.ReaderCount++;
                    ExitLock();
                    return true;
                }
                if (managedThreadId == _upgradeLockOwnerId)
                {
                    lrwc.ReaderCount++;
                    _owners++;
                    ExitLock();
                    _upgradeThreadHoldingRead = true;
                    return true;
                }
                if (managedThreadId == _writeLockOwnerId)
                {
                    lrwc.ReaderCount++;
                    _owners++;
                    ExitLock();
                    return true;
                }
            }
            var flag = true;
            var spinCount = 0;
            Label_012E:
            if (_owners < MaxReaderCount)
            {
                _owners++;
                lrwc.ReaderCount++;
            }
            else
            {
                if (spinCount < MaxSpinCount)
                {
                    ExitLock();
                    if (timeout.IsExpired)
                    {
                        return false;
                    }
                    spinCount++;
                    SpinWait(spinCount);
                    EnterLock();
                    if (IsRwHashEntryChanged(lrwc, managedThreadId))
                    {
                        lrwc = GetThreadRwCount(managedThreadId, false);
                    }
                }
                else if (_readEvent == null)
                {
                    LazyCreateEvent(ref _readEvent, false);
                    if (IsRwHashEntryChanged(lrwc, managedThreadId))
                    {
                        lrwc = GetThreadRwCount(managedThreadId, false);
                    }
                }
                else
                {
                    flag = WaitOnEvent(_readEvent, ref _numReadWaiters, timeout);
                    if (!flag)
                    {
                        return false;
                    }
                    if (IsRwHashEntryChanged(lrwc, managedThreadId))
                    {
                        lrwc = GetThreadRwCount(managedThreadId, false);
                    }
                }
                goto Label_012E;
            }
            ExitLock();
            return flag;
        }

        public bool TryEnterUpgradeableReadLock(int millisecondsTimeout)
        {
            return TryEnterUpgradeableReadLock(new TimeoutTracker(millisecondsTimeout));
        }

        private bool TryEnterUpgradeableReadLock(TimeoutTracker timeout)
        {
            return TryEnterUpgradeableReadLockCore(timeout);
        }

        public bool TryEnterUpgradeableReadLock(TimeSpan timeout)
        {
            return TryEnterUpgradeableReadLock(new TimeoutTracker(timeout));
        }

        private bool TryEnterUpgradeableReadLockCore(TimeoutTracker timeout)
        {
            ReaderWriterCount threadRwCount;
            ThrowIfDisposed();
            
            var threadId = Thread.CurrentThread.ManagedThreadId;
            if (!_isReentrant)
            {
                threadRwCount = VerifyCanEnterNonReentrantUpgradeableReadLock(threadId);
            }
            else
            {
                EnterLock();
                
                // if the current thread is the upgrade lock owner then increment the lock count and return
                threadRwCount = GetThreadRwCount(threadId, false);
                if (threadId == _upgradeLockOwnerId)
                {
                    threadRwCount.RecursiveCounts.UpgradeCount++;
                    ExitLock();
                    return true;
                }

                // if the current thread is the write lock owner then increment lock count and return
                if (threadId == _writeLockOwnerId)
                {
                    _owners++;
                    _upgradeLockOwnerId = threadId;
                    threadRwCount.RecursiveCounts.UpgradeCount++;
                    if (threadRwCount.ReaderCount > 0)
                    {
                        _upgradeThreadHoldingRead = true;
                    }
                    ExitLock();
                    return true;
                }

                // if the current thread has a read lock then throw
                if (threadRwCount.ReaderCount > 0)
                {
                    ExitLock();
                    throw new LockRecursionException(PlatformExceptionMessages.ReaderWriterLock_UpgradeableLockNotAllowedWhenReadLockHeld);
                }
            }

            // couldn't acquire lock so wait....
            var spinCount = 0;
            while (true)
            {
                if (_upgradeLockOwnerId == -1 && _owners < MaxReaderCount)
                {
                    _owners++;
                    _upgradeLockOwnerId = threadId;
                    break;
                }
                
                if (spinCount < MaxSpinCount)
                {
                    ExitLock();
                    if (timeout.IsExpired)
                    {
                        return false;
                    }
                    spinCount++;
                    SpinWait(spinCount);
                    EnterLock();
                    continue;
                }

                if (_upgradeEvent == null)
                {
                    LazyCreateEvent(ref _upgradeEvent, true);
                    continue;
                }

                if (WaitOnEvent(_upgradeEvent, ref _numUpgradeWaiters, timeout))
                {
                    continue;
                }
                return false;
            }

            if (_isReentrant)
            {
                if (IsRwHashEntryChanged(threadRwCount, threadId))
                {
                    threadRwCount = GetThreadRwCount(threadId, false);
                }
                threadRwCount.RecursiveCounts.UpgradeCount++;
            }
            ExitLock();
            return true;
        }

        private void ThrowIfDisposed()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(null);
            }
        }

        private ReaderWriterCount VerifyCanEnterNonReentrantUpgradeableReadLock(int threadId)
        {
            if (threadId == _upgradeLockOwnerId)
            {
                throw new LockRecursionException(PlatformExceptionMessages.ReaderWriterLock_RecursiveUpgradeableLockNotAllowed);
            }
            
            if (threadId == _writeLockOwnerId)
            {
                throw new LockRecursionException(
                    PlatformExceptionMessages.ReaderWriterLock_UpgradeableLockNotAllowedWhenWriteLockHeld);
            }

            EnterLock();
            var threadRwCount = GetThreadRwCount(threadId, true);
            if (threadRwCount != null && threadRwCount.ReaderCount > 0)
            {
                ExitLock();
                throw new LockRecursionException(PlatformExceptionMessages.ReaderWriterLock_UpgradeableLockNotAllowedWhenReadLockHeld);
            }
            return threadRwCount;
        }

        public bool TryEnterWriteLock(int millisecondsTimeout)
        {
            return TryEnterWriteLock(new TimeoutTracker(millisecondsTimeout));
        }

        private bool TryEnterWriteLock(TimeoutTracker timeout)
        {
            return TryEnterWriteLockCore(timeout);
        }

        public bool TryEnterWriteLock(TimeSpan timeout)
        {
            return TryEnterWriteLock(new TimeoutTracker(timeout));
        }

        private bool TryEnterWriteLockCore(TimeoutTracker timeout)
        {
            ReaderWriterCount threadRwCount;
            if (_isDisposed)
            {
                throw new ObjectDisposedException(null);
            }
            var currentThreadId = Thread.CurrentThread.ManagedThreadId;
            var isCurrentThreadUpdateLockOwner = false;
            if (!_isReentrant)
            {
                if (currentThreadId == _writeLockOwnerId)
                {
                    throw new LockRecursionException(PlatformExceptionMessages.ReaderWriterLock_RecursiveWriteLockNotAllowed);
                }
                if (currentThreadId == _upgradeLockOwnerId)
                {
                    isCurrentThreadUpdateLockOwner = true;
                }
                EnterLock();
                threadRwCount = GetThreadRwCount(currentThreadId, true);
                if (threadRwCount != null && threadRwCount.ReaderCount > 0)
                {
                    ExitLock();
                    throw new LockRecursionException(PlatformExceptionMessages.ReaderWriterLock_WriteLockNotAllowedWhenReadLockHeld);
                }
            }
            else
            {
                EnterLock();
                threadRwCount = GetThreadRwCount(currentThreadId, false);
                if (currentThreadId == _writeLockOwnerId)
                {
                    threadRwCount.RecursiveCounts.WriterCount++;
                    ExitLock();
                    return true;
                }
                if (currentThreadId == _upgradeLockOwnerId)
                {
                    isCurrentThreadUpdateLockOwner = true;
                }
                else if (threadRwCount.ReaderCount > 0)
                {
                    ExitLock();
                    throw new LockRecursionException(
                        PlatformExceptionMessages.ReaderWriterLock_WriteLockNotAllowedWhenReadLockHeld);
                }
            }
            var spinCount = 0;
            while (true)
            {
                if (IsWriterAcquired())
                {
                    SetOwnerFlag(WriterHeld);
                    break;
                }

                if (isCurrentThreadUpdateLockOwner)
                {
                    var numReaders = GetNumReaders();
                    if (numReaders == 1)
                    {
                        SetOwnerFlag(WriterHeld);
                        break;
                    }
                    if ((numReaders == 2) && (threadRwCount != null))
                    {
                        if (IsRwHashEntryChanged(threadRwCount, currentThreadId))
                        {
                            threadRwCount = GetThreadRwCount(currentThreadId, false);
                        }
                        if (threadRwCount.ReaderCount > 0)
                        {
                            SetOwnerFlag(WriterHeld);
                            break;
                        }
                    }
                }
                if (spinCount < MaxSpinCount)
                {
                    ExitLock();
                    if (timeout.IsExpired)
                    {
                        return false;
                    }
                    spinCount++;
                    SpinWait(spinCount);
                    EnterLock();
                    continue;
                }
                if (isCurrentThreadUpdateLockOwner)
                {
                    if (_waitUpgradeEvent != null)
                    {
                        if (!WaitOnEvent(_waitUpgradeEvent, ref _numWriteUpgradeWaiters, timeout))
                        {
                            return false;
                        }
                    }
                    else
                    {
                        LazyCreateEvent(ref _waitUpgradeEvent, true);
                    }
                    continue;
                }
                if (_writeEvent == null)
                {
                    LazyCreateEvent(ref _writeEvent, true);
                    continue;
                }
                if (WaitOnEvent(_writeEvent, ref _numWriteWaiters, timeout))
                {
                    continue;
                }
                return false;
            }
            if (_isReentrant)
            {
                if (IsRwHashEntryChanged(threadRwCount, currentThreadId))
                {
                    threadRwCount = GetThreadRwCount(currentThreadId, false);
                }
                threadRwCount.RecursiveCounts.WriterCount++;
            }
            ExitLock();
            _writeLockOwnerId = currentThreadId;
            return true;
        }

        private bool WaitOnEvent(EventWaitHandle waitEvent, ref uint waiterCount, TimeoutTracker timeout)
        {
            waitEvent.Reset();
            waiterCount++;
            if (_numWriteWaiters == 1)
            {
                SetOwnerFlag(WaitingWriters);
            }
            if (_numWriteUpgradeWaiters == 1)
            {
                SetOwnerFlag(WaitingUpgrader);
            }
            var isSignaled = false;
            ExitLock();
            try
            {
                isSignaled = waitEvent.WaitOne(timeout.RemainingMilliseconds);
            }
            finally
            {
                EnterLock();
                waiterCount--;
                _noWaiters = _numWriteWaiters == 0 && _numWriteUpgradeWaiters == 0 && _numUpgradeWaiters == 0 && _numReadWaiters == 0;
                if (_numWriteWaiters == 0)
                {
                    ClearOwnerFlag(WaitingWriters);
                }
                if (_numWriteUpgradeWaiters == 0)
                {
                    ClearOwnerFlag(WaitingUpgrader);
                }
                if (!isSignaled)
                {
                    ExitLock();
                }
            }
            return isSignaled;
        }

        #region Nested type: TimeoutTracker

        [StructLayout(LayoutKind.Sequential)]
        private struct TimeoutTracker
        {
            private readonly int _total;
            private readonly int _start;

            public TimeoutTracker(TimeSpan timeout)
            {
                var totalMilliseconds = (long)timeout.TotalMilliseconds;
                if ((totalMilliseconds < -1L) || (totalMilliseconds > 0x7fffffffL))
                {
                    throw new ArgumentOutOfRangeException("timeout");
                }
                _total = (int)totalMilliseconds;
                if ((_total != -1) && (_total != 0))
                {
                    _start = Environment.TickCount;
                }
                else
                {
                    _start = 0;
                }
            }

            public TimeoutTracker(int millisecondsTimeout)
            {
                if (millisecondsTimeout < -1)
                {
                    throw new ArgumentOutOfRangeException("millisecondsTimeout");
                }
                _total = millisecondsTimeout;
                if ((_total != -1) && (_total != 0))
                {
                    _start = Environment.TickCount;
                }
                else
                {
                    _start = 0;
                }
            }

            public int RemainingMilliseconds
            {
                get
                {
                    if ((_total == -1) || (_total == 0))
                    {
                        return _total;
                    }
                    var num = Environment.TickCount - _start;
                    if ((num >= 0) && (num < _total))
                    {
                        return (_total - num);
                    }
                    return 0;
                }
            }

            public bool IsExpired
            {
                get { return (RemainingMilliseconds == 0); }
            }
        }

        #endregion
    }

    internal class ReaderWriterCount
    {
        public ReaderWriterCount Next;
        public int ReaderCount;
        public RecursiveCounts RecursiveCounts;
        public int ThreadId = -1;

        public ReaderWriterCount(bool fIsReentrant)
        {
            if (fIsReentrant)
            {
                RecursiveCounts = new RecursiveCounts();
            }
        }

        public bool IsEmpty
        {
            get
            {
                return ((ThreadId == -1) ||
                    (((ReaderCount == 0) && (RecursiveCounts == null)) ||
                        (((ReaderCount == 0) && (RecursiveCounts.WriterCount == 0)) && (RecursiveCounts.UpgradeCount == 0))));
            }
        }

        public bool HasThreadId(int threadId)
        {
            return (ThreadId != threadId);
        }
    }

    internal class RecursiveCounts
    {
        public int UpgradeCount;
        public int WriterCount;
    }
}